package com.github_dididipapa.mall.product.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.github_dididipapa.common.utils.RandomTime;
import com.github_dididipapa.mall.product.dto.CatelogTreeDto;
import com.github_dididipapa.mall.product.service.CategoryBrandRelationService;
import com.github_dididipapa.mall.product.vo.CatelogTreeVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github_dididipapa.common.utils.PageUtils;
import com.github_dididipapa.common.utils.Query;
import com.github_dididipapa.mall.product.dao.CategoryDao;
import com.github_dididipapa.mall.product.entity.CategoryEntity;
import com.github_dididipapa.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Slf4j
@RequiredArgsConstructor
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Lazy
    @Autowired
    private CategoryService categoryService;
    private final CategoryBrandRelationService categoryBrandRelationService;
    private final CategoryDao categoryDao;
    private final RedissonClient redisson;
    private final StringRedisTemplate redisTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //组装成父子树形结构
        //1级分类
        List<CategoryEntity> level1Menus = entities.stream().
                filter(entity -> entity.getParentCid() == 0).
                peek(entity -> entity.setChildren(getChildren(entity, entities))).
                sorted((entity1, entity2) ->
                        (entity1.getSort() == null ? 0 : entity1.getSort()) -
                                (entity2.getSort() == null ? 0 : entity2.getSort())).
                collect(Collectors.toList());
        return level1Menus;
    }

    @Override
    public void removeMenuByIds(List<Long> list) {
        baseMapper.deleteBatchIds(list);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        paths = findParentId(catelogId, paths);
        Collections.reverse(paths);
        return paths.toArray(Long[]::new);
    }

    @Transactional
    @Override
    public void updateCascde(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    @Override
    public List<Long> getCatelogPath(Long catelogId) {
        List<Long> result = new ArrayList<>();

        while(catelogId != 0){
            result.add(catelogId);
            CategoryEntity categoryEntity = this.getById(catelogId);
            if(categoryEntity != null) catelogId = categoryEntity.getParentCid();
        }

        Collections.reverse(result);
        return result;
    }


    @Cacheable(cacheNames = "cate#*600", key = "'level1_catelogs'")
    @Override
    public List<CategoryEntity> getLevelOneClassOfProduct() {
        return categoryService
                .getBaseMapper()
                .selectList(
                        new QueryWrapper<CategoryEntity>()
                                .eq("parent_cid", 0)
                );
    }

    /**
     * @description: 封装Vo数据成树形结构
     * @author: dididi_papa
     * @date: 2025/7/8 下午5:06
     * @param: []
     * @return: java.util.Map<java.lang.String,java.util.List<com.github_dididipapa.mall.product.vo.CatelogListTreeVo>>
     **/
    @Cacheable(cacheNames = "cate#36000", key = "'catelogJson'")
    @Override
    public Map<String, List<CatelogTreeVo>> getCatelogJson() {
        // 缓存穿透：缓存数据不存在，导致大量请求打入数据库。对请求设置空值缓存并设置短暂过期时间，避免穿透。
        // 缓存击穿：热点缓存数据失效的那一刻，因找不到缓存数据进而导致大量请求打入数据库。通过对每个请求添加锁，避免击穿。
        // 缓存雪崩：缓存同一时间集体失效，导致大量请求打入数据库。给缓存添加随机时间过期，避免缓存雪崩。
        String cachedJson =  redisTemplate.opsForValue().get("cate:catelogJson") ;//jedis.get("cate:catelogJson");
        if (cachedJson != null && !cachedJson.isEmpty()) {
            return JSON.parseObject(cachedJson,
                    new TypeReference<Map<String, List<CatelogTreeVo>>>() {});
        }
        // 分布式双检锁
        // 加锁避免缓存击穿
        RLock r = redisson.getLock("readOnly-catelog");
        Map<String, List<CatelogTreeVo>> collect = new HashMap<>();
        r.lock();
        try {
            Thread.sleep(1);
            cachedJson = redisTemplate.opsForValue().get("cate:catelogJson");
            if (cachedJson != null && !cachedJson.isEmpty()) {
                return JSON.parseObject(cachedJson,
                        new TypeReference<Map<String, List<CatelogTreeVo>>>() {});
            }

            // 查询出所有的catelog， 避免 N+1 查询
            List<CatelogTreeDto> catelogTree = categoryDao.getCatelogTree();
            // 收集父节点
            Stream<CatelogTreeDto> parentStream = catelogTree.stream()
                    .filter(catelogTreeDto -> catelogTreeDto.getParentId().equals(0L));

            // 创建vo容器
            collect = parentStream.collect(Collectors.toMap(CatelogTreeDto::getId, v -> {
                String id = v.getId();
                // 获取所有二级分类
                List<CatelogTreeVo> list = catelogTree.stream()
                        .filter(catelogTreeDto -> catelogTreeDto.getParentId().toString().equals(id))
                        .map(catelogTreeDto -> {
                            CatelogTreeVo vo = new CatelogTreeVo();
                            vo.setCatelog1Id(catelogTreeDto.getParentId().toString());
                            vo.setId(catelogTreeDto.getId());
                            vo.setName(catelogTreeDto.getName());
                            // 获取所有三级分类
                            List<CatelogTreeVo.Catelog3Vo> catelog3Vos = catelogTree.stream()
                                    .filter(item -> item.getParentId().toString().equals(vo.getId()))
                                    .map(item -> {
                                        CatelogTreeVo.Catelog3Vo catelog3Vo = new CatelogTreeVo.Catelog3Vo();
                                        catelog3Vo.setCatelog2Id(item.getParentId().toString());
                                        catelog3Vo.setId(item.getId());
                                        catelog3Vo.setName(item.getName());
                                        return catelog3Vo;
                                    })
                                    .collect(Collectors.toList());
                            vo.setCatalog3List(catelog3Vos);
                            return vo;
                        })
                        .collect(Collectors.toList());
                return list;
            }));
            String cache = JSON.toJSONString(collect);
            long time = RandomTime.RANDOM_SECONDS.getTime();
            redisTemplate.opsForValue().set("cate:catelogJson", cache, 3600 * 6 + time, TimeUnit.SECONDS);
        }
        catch (Exception e) {
            log.info("缓存或数据库查询异常:{}",e.getMessage());
        }
        finally {
            r.unlock();
        }
        return collect;
    }

    //递归查找父节点
    private List<Long> findParentId(Long categoryId, List<Long> paths) {
        paths.add(categoryId);
        CategoryEntity preId = this.getById(categoryId);
        if (preId.getParentCid() != 0) {
            findParentId(preId.getParentCid(),  paths);
        }
        return paths;
    }

    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter(entity -> entity.getParentCid().equals(root.getCatId())).
                map(entity -> {
                    entity.setChildren(getChildren(entity, all));
                    return entity;
                }).sorted((entity1, entity2) ->
                        (entity1.getSort() == null ? 0 : entity1.getSort()) -
                                (entity2.getSort() == null ? 0 : entity2.getSort())).
                collect(Collectors.toList());
        return children;
    }

}